﻿#region 참조 네임스페이스

using Microsoft.Build.Tasks.Deployment.ManifestUtilities;
using Skying.ComponentModel;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;

#endregion

namespace Skying.Reflection.Update
{
	/// <summary>
	/// 어플리케이션 빌드 시에 생성되는 매니페스트 파일을 통해 어플리케이션의 실제 <see cref="System.Reflection.Assembly"/> 개체와 비교하여 업데이트가 필요한지를 점검하기 위한 인터페이스를 제공한다.
	/// </summary>
	/// <remarks>
	/// <para><see cref="Skying.Reflection.Update.ManifestChecker"/> 클래스를 업데이트가 필요한 어플리케이션에 적용해야 한다.</para>
	/// <para><see cref="Skying.Reflection.Update.ManifestChecker.UpdateRequired"/>가 실행되면 어플리케이션 정책에 따라 업데이트 진행 여부를 결정하면 된다.</para>
	/// </remarks>
	class ManifestChecker
	{
		#region 업데이트 점검

		/// <summary>
		/// 실행 중인 어플리케이션의 <see cref="System.Reflection.Assembly"/> 개체와 어플리케이션에 대한 <see cref="Microsoft.Build.Tasks.Deployment.ManifestUtilities.Manifest"/>의 <see cref="System.IO.Stream"/>을 통해 업데이트가 필요한지를 점검한다.
		/// </summary>
		/// <param name="executedAssembly">실행 중인 어플리케이션의 <see cref="System.Reflection.Assembly"/> 개체를 설정한다.</param>
		/// <param name="manifestStream">
		/// <para>어플리케이션에 대한 <see cref="Microsoft.Build.Tasks.Deployment.ManifestUtilities.Manifest"/>의 <see cref="System.IO.Stream"/> 개체를 설정한다.</para>
		/// <para>여기서 말하는 <see cref="Microsoft.Build.Tasks.Deployment.ManifestUtilities.Manifest"/>의 <see cref="System.IO.Stream"/> 개체란 어플리케이션 빌드 시 생성되는 manifest 파일의 <see cref="System.IO.Stream"/> 개체를 말한다.</para>
		/// </param>
		/// <param name="reservedFiles"></param>
		public static bool Check(Assembly executedAssembly, Stream manifestStream, out IList<Assembly> reservedFiles)
		{
			reservedFiles = new List<Assembly>();

			// 스트림으로부터 매니페스트 정보를 읽어들인다.
			Manifest manifest = ManifestReader.ReadManifest(manifestStream, false);

			// 실행 중인 어플리케이션의 AssemblyName 개체를 가져온다.
			AssemblyName executedAssemblyName = executedAssembly.GetName();

			// 매니페스트 정보로부터 실행 중인 어플리케이션의 AssemblyReference 개체를 찾아낸다.
			AssemblyReference executedAssemblyReference = manifest.AssemblyReferences.Find(executedAssemblyName.Name);

			// 실행 중인 어플리케이션의 실제 경로를 통해 실행 파일 정보를 획득한다.
			FileInfo executedAssemblyFileInfo = new FileInfo(executedAssembly.Location);

			// 실행 중인 어플리케이션의 Assembly 버전 정보와 매니페스트 상의 버전 정보 혹은 실제 파일 사이즈와 매니페스트 상의 사이즈 정보가 일치하지 않는 경우 Assembly 정보를 업데이트 항목에 추가한다.
			if (!executedAssemblyName.Version.Equals(executedAssemblyReference.AssemblyIdentity.Version) ||
				executedAssemblyFileInfo.Length != executedAssemblyReference.Size)
			{
				reservedFiles.Add(executedAssembly);
			}

			// 실행 중인 어플리케이션의 Assembly를 통해 참조 중인 Assembly들의 AssemblyName 목록을 가져온다.
			AssemblyName[] assemblyNames = executedAssembly.GetReferencedAssemblies();

			// 매니페스트 상에서 참조 Assembly 들의 목록을 조회한다.
			foreach (AssemblyReference assemblyReference in manifest.AssemblyReferences)
			{
				// 실행 중인 어플리케이션의 참조 AssemblyName 목록에서 매니페스트 상의 참조 Assembly와 일치하는 항목이 있는지를 찾는다.
				AssemblyName referencedAssemblyName = assemblyNames.SingleOrDefault(t => t.Name.Equals(assemblyReference.AssemblyIdentity.Name));

				if (referencedAssemblyName != null)
				{
					Assembly referencedAssembly = Assembly.Load(referencedAssemblyName);

					// 참조 Assembly의 실제 경로를 통해 Assembly의 파일 정보를 획득한다.
					FileInfo referencedAssemblyFileInfo = new FileInfo(referencedAssembly.Location);

					// 참조 Assembly 버전 정보와 매니페스트 상의 참조 Assembly 버전 정보 혹은 둘 간의 실제 파일 사이즈가 다른 경우 Assembly 정보를 업데이트 항목에 추가한다.
					if (!referencedAssemblyName.Version.Equals(assemblyReference.AssemblyIdentity.Version) ||
						referencedAssemblyFileInfo.Length != assemblyReference.Size)
					{
						reservedFiles.Add(referencedAssembly);
					}
				}
			}

			return reservedFiles.Count > 0;
		}

		#endregion
	}
}